<?php
	include('magpierss/rss_fetch.inc');

	class FullRSS
	{
		private $url='';
		private $initData='';
		private $cacheDir='';
		private $cacheFile='';
		private $lockFile='';
		private $error='';
		private $fl=null;
		private $pubDate='';
		private $lastBuildDate='';
		private $firstItemDate='';
		private $allUpdate=true;
		private $itemFail=false;

		//构造函数
		function __construct($url, $initData, $cacheDir, $cacheFile, $lockFile)
		{
			$this->url = $url;
			$this->initData = $initData;
			$this->cacheDir = $cacheDir;
			$this->cacheFile = $cacheFile;
			$this->lockFile = $lockFile;
		}

		//流程函数
		function GetFullRSS ()
		{
			if ( $this->InitCache ())//初始化缓存成功
			{
				define('MAGPIE_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)');
				define('MAGPIE_FETCH_TIME_OUT', 30);
				set_time_limit(MAX_FETCH*30+30);

				switch ( $this->Updating() )//是否正在更新
				{
					case 0:
						$rss = fetch_rss($this->url);
						switch($this->FeedNeedRenew($rss))
						{
							case 0://无需更新
								break;
							case 1://需要更新
								$rss->items = array_reverse($rss->items);
								//$item=$rss->items[0];
								$n=0;//计数清零
								foreach($rss->items as $item)
								{
									switch($this->ItemNeedRenew($item['guid'], $item['pubdate']))//需要下载全文的item
									{
										case 0://无需更新
											break;
										case 1://需要更新
											if($n<MAX_FETCH)//每次更新的item数，避免等待时间过长
											{
												//获取item
												$link=$item['link'];
												$description=GetItem($link);

												if($description=='')
												{
													//把feed时间设为最后一个成功更新的item时间
													if($this->lastBuildDate=='')
														$this->pubDate=$this->firstItemDate;
													else
														$this->lastBuildDate=$this->firstItemDate;
													$this->itemFail=true;
												}
												else if( ! $this->UpdateItem($item, $description))
													echo $this->error;

												$n++;//计数
												//echo $n . '<br>';//输出计数
											}
											else
												//未全部更新
												$this->allUpdate=false;
											break;
										case 2://出错
											echo $this->error;
									}
								}
								break;
							case 2://出错
								echo $this->error;
						}
						//更新feed时间
						//if($this->allUpdate)
							if( ! $this->UpdateFeedDate())
								echo $this->error;
						$this->Unlock();//解锁
						//break;
					case 1://等待更新完成，输出feed
						//if(!$this->allUpdate)//未全部更新，不输出rss
						//	echo "Update does not finish, please refresh <br />\r\n";
						//else
						{
							if( $this->UpdateFinish() )
							{
								if( ! $this->Output() )
									echo $this->error;
								$this->Unlock();//解锁
							}
							else
								echo $this->error;
						}
						break;
					case 2://无法打开flock
						echo $this->error;
				}
			}
			else//初始化缓存失败
				echo $this->error;
		}

		//初始化缓存
		function InitCache ()
		{
			if ( ! file_exists($this->cacheDir) )//cache目录
			{
				$status = @mkdir($this->cacheDir, 0755 );
				// if make failed 
				if ( ! $status )
				{
					if ( ! file_exists($this->cacheDir) )//是否有其它线程创建了目录
					{
						$this->error .= "Cache couldn't make dir!" . '<br />' . "\r\n";
						return false;
					}
				}
			}

			if ( ! file_exists($this->cacheDir . '/' . $this->cacheFile) )//cache文件
			{
				$fp = @fopen( $this->cacheDir . '/' . $this->cacheFile, 'w' );
				if( ! $fp )
				{
					$this->error .= "Cache couldn't make file!" . '<br />' . "\r\n";
					return false;
				}
				else
				{
					if(flock($fp, LOCK_EX|LOCK_NB))
					{
						fwrite($fp, $this->initData);
						flock($fp, LOCK_UN);
					}
					fclose($fp);
				}
			}

			if ( ! file_exists($this->cacheDir . '/' . $this->lockFile) )//lock文件
			{
				$fp = @fopen( $this->cacheDir . '/' . $this->lockFile, 'w' );
				if( ! $fp )
				{
					$this->error .= "Couldn't make lockFile!" . '<br />' . "\r\n";
					return false;
				}
				else
					fclose($fp);
			}

			return true;
		}

		//cache正在更新或读取
		function Updating()
		{
			$this->fl = @fopen( $this->cacheDir . '/' . $this->lockFile, 'r' );
			if( ! $this->fl )
			{
				$this->error .= "Couldn't open lockFile!" . '<br />' . "\r\n";
				return 2;
			}
			if( !flock($this->fl, LOCK_EX|LOCK_NB))
			{
				fclose($this->fl);
				return 1;
			}
			return 0;
		}

		//cache更新完成
		function UpdateFinish()
		{
			$this->fl = @fopen( $this->cacheDir . '/' . $this->lockFile, 'r' );
			if( ! $this->fl )
			{
				$this->error .= "Couldn't open lockFile!" . '<br />' . "\r\n";
				return false;
			}
			for($i=0;$i<20;$i++)//等待20秒
			{
				if(flock($this->fl, LOCK_SH|LOCK_NB))
					return true;
				sleep(1);
			}
			$this->error .= 'Wait time out!' . '<br />' . "\r\n";
			return false;
		}

		//feed需要更新
		function FeedNeedRenew($rss)
		{
			$xmlDoc = new DOMDocument();
			if( ! $xmlDoc->load($this->cacheDir . '/' . $this->cacheFile))
			{
				$this->error .= "Couldn't load XML!" . '<br />' . "\r\n";
				return 2;
			}
			$channel = $xmlDoc->getElementsByTagName('channel')->item(0);
			$state=0;
			foreach($channel->getElementsByTagName('lastBuildDate') As $lastBuildDate)
			{
				if(@strtotime($lastBuildDate->nodeValue) < @strtotime($rss->channel['lastbuilddate']))
				{
					$this->firstItemDate=$lastBuildDate->nodeValue;
					$this->lastBuildDate=$rss->channel['lastbuilddate'];
					$state=1;
				}
			}
			foreach($channel->getElementsByTagName('pubDate') As $pubDate)
			{
				if(@strtotime($pubDate->nodeValue) < @strtotime($rss->channel['pubdate']))
				{
					$this->pubDate=$rss->channel['pubdate'];
					if($state==0)
					{
						$this->firstItemDate=$pubDate->nodeValue;
						$state=1;
					}
				}
			}
			return $state;
		}

		//Item需要更新
		function ItemNeedRenew($guid, $pubDate)
		{
			$xmlDoc = new DOMDocument();
			$xmlDoc->preserveWhiteSpace=false;
			$xmlDoc->formatOutput=true;
			if( ! $xmlDoc->load($this->cacheDir . '/' . $this->cacheFile))
			{
				$this->error .= "Couldn't load XML!" . '<br />' . "\r\n";
				return 2;
			}
			$channel = $xmlDoc->getElementsByTagName('channel')->item(0);
			foreach($channel->getElementsByTagName('item') As $item)
				if($item->getElementsByTagName('guid')->item(0)->nodeValue == $guid)
					if($item->getElementsByTagName('pubDate')->item(0)->nodeValue == $pubDate)
						return 0;
					else
					{
						$channel->removeChild($item);
						if( ! $xmlDoc->save($this->cacheDir . '/' . $this->cacheFile))
						{
							$this->error .= "Couldn't save XML!" . '<br />' . "\r\n";
							return 2;
						}
					}
			return 1;
		}

		//更新Item
		function UpdateItem($item, $description)
		{
			$xmlDoc = new DOMDocument();
			$xmlDoc->preserveWhiteSpace=false;
			$xmlDoc->formatOutput=true;
			if( ! $xmlDoc->load($this->cacheDir . '/' . $this->cacheFile))
			{
				$this->error .= "Couldn't load XML!" . '<br />' . "\r\n";
				return false;
			}

			//合成item
			$newItem=$xmlDoc->createElement('item');
			if (array_key_exists('title', $item)) 
			{
				$newNode=$xmlDoc->createElement('title', htmlspecialchars($item['title']));
				$newItem->appendChild($newNode);
			}
			if (array_key_exists('link', $item)) 
			{
				$newNode=$xmlDoc->createElement('link', htmlspecialchars($item['link']));
				$newItem->appendChild($newNode);
			}
			$newNode=$xmlDoc->createElement('description');
			$newNode->appendChild($xmlDoc->createCDATASection (preg_replace('/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/', '', $description)));//过滤控制字符
			$newItem->appendChild($newNode);
			if (array_key_exists('author', $item)) 
			{
				$newNode=$xmlDoc->createElement('author', htmlspecialchars($item['author']));
				$newItem->appendChild($newNode);
			}
			else if (array_key_exists('dc', $item) && array_key_exists('creator', $item['dc']))
			{
				$newNode=$xmlDoc->createElement('author', htmlspecialchars($item['dc']['creator']));
				$newItem->appendChild($newNode);
			}
			if (array_key_exists('category', $item)) 
			{
				$newNode=$xmlDoc->createElement('category');
				$newNode->appendChild($xmlDoc->createCDATASection ($item['category']));
				$newItem->appendChild($newNode);
			}
			if (array_key_exists('comments', $item)) 
			{
				$newNode=$xmlDoc->createElement('comments', htmlspecialchars($item['comments']));
				$newItem->appendChild($newNode);
			}
			if (array_key_exists('guid', $item)) 
			{
				$newNode=$xmlDoc->createElement('guid', htmlspecialchars($item['guid']));
				$newItem->appendChild($newNode);
			}
			if (array_key_exists('pubdate', $item)) 
			{
				$newNode=$xmlDoc->createElement('pubDate', htmlspecialchars($item['pubdate']));
				$newItem->appendChild($newNode);
			}
			
			//插入item
			$channel = $xmlDoc->getElementsByTagName('channel')->item(0);
			$itemList = $channel->getElementsByTagName('item');
//			if($itemList->length == 0)
//				$channel->appendChild($newItem);
//			else
			$insert=false;
			$n=0;
			foreach($itemList As $oldItem)
			{
				if(@strtotime($oldItem->getElementsByTagName('pubDate')->item(0)->nodeValue) < @strtotime($newItem->getElementsByTagName('pubDate')->item(0)->nodeValue))
				{
					$channel->insertBefore($newItem, $oldItem);
					$insert=true;
					break;
				}
				$n++;
			}
			if(!$insert)
				$channel->appendChild($newItem);

			//item缓存总数
			for($i=$itemList->length;$i>MAX_ITEMS;$i--)
				$channel->removeChild($channel->lastChild);

			//更新feed时间
			if($n==0)
			{
				$this->firstItemDate=$item['pubdate'];

				$lastBuildDate=$channel->getElementsByTagName('lastBuildDate');
				if($lastBuildDate->length>0)
					$lastBuildDate->item(0)->nodeValue = $item['pubdate'];
				else
					$channel->getElementsByTagName('pubDate')->item(0)->nodeValue = $item['pubdate'];
			}

			if( ! $xmlDoc->save($this->cacheDir . '/' . $this->cacheFile))
			{
				$this->error .= "Couldn't save XML!" . '<br />' . "\r\n";
				return false;
			}
			return true;
		}

		//输出feed
		function Output()
		{
			$xmlDoc = new DOMDocument();
			$xmlDoc->preserveWhiteSpace=false;
			$xmlDoc->formatOutput=true;
			if( ! $xmlDoc->load($this->cacheDir . '/' . $this->cacheFile))
			{
				$this->error .= "Couldn't load XML!" . '<br />' . "\r\n";
				return false;
			}
			
			header("Content-type: text/xml; charset=UTF-8");
			echo $xmlDoc->saveXML();
			return true;
		}

		//解锁
		function Unlock()
		{
			flock($this->fl, LOCK_UN);
			fclose($this->fl);
		}

		//更新feed时间
		function UpdateFeedDate()
		{
			$xmlDoc = new DOMDocument();
			$xmlDoc->preserveWhiteSpace=false;
			$xmlDoc->formatOutput=true;
			if( ! $xmlDoc->load($this->cacheDir . '/' . $this->cacheFile))
			{
				$this->error .= "Couldn't load XML!" . '<br />' . "\r\n";
				return false;
			}

			$channel = $xmlDoc->getElementsByTagName('channel')->item(0);

			if($this->lastBuildDate !='' && ($this->allUpdate || $this->itemFail))
				$channel->getElementsByTagName('lastBuildDate')->item(0)->nodeValue = $this->lastBuildDate;
			if($this->pubDate !='' && ($this->allUpdate || $this->itemFail))
				$channel->getElementsByTagName('pubDate')->item(0)->nodeValue = $this->pubDate;

			if( ! $xmlDoc->save($this->cacheDir . '/' . $this->cacheFile))
			{
				$this->error .= "Couldn't save XML!" . '<br />' . "\r\n";
				return false;
			}
			return true;
		}
	}
?>
